using System;

namespace UnityEngine
{
	// State for when we're dragging a slider.
	internal class SliderState
	{
		public float dragStartPos;
		public float dragStartValue;
		public bool isDragging;
	}

	// TODO: Make the thumb positioning / sizing be right
	internal struct SliderHandler
	{
		readonly Rect position;
		readonly float currentValue;
		readonly float size;
		readonly float start;
		readonly float end;
		readonly GUIStyle slider;
		readonly GUIStyle thumb;
		readonly bool horiz;
		readonly int id;

		public SliderHandler(Rect position, float currentValue, float size, float start, float end, GUIStyle slider, GUIStyle thumb, bool horiz, int id)
		{
			this.position = position;
			this.currentValue = currentValue;
			this.size = size;
			this.start = start;
			this.end = end;
			this.slider = slider;
			this.thumb = thumb;
			this.horiz = horiz;
			this.id = id;
		}

		public float Handle()
		{
			if (slider == null || thumb == null)
				return currentValue;

			switch (CurrentEventType())
			{
				case EventType.MouseDown:
					return OnMouseDown();

				case EventType.MouseDrag:
					return OnMouseDrag();

				case EventType.MouseUp:
					return OnMouseUp();

				case EventType.Repaint:
					return OnRepaint();
			}
			return currentValue;
		}

		private float OnMouseDown()
		{
			// if the click is outside this control, just bail out...
			if (!position.Contains(CurrentEvent().mousePosition) || IsEmptySlider())
				return currentValue;

			GUI.scrollTroughSide = 0;
			GUIUtility.hotControl = id;
			CurrentEvent().Use();
			
			if (ThumbSelectionRect().Contains(CurrentEvent().mousePosition))
			{
				// We have a mousedown on the thumb
				// Record where we're draging from, so the user can get back.
				StartDraggingWithValue(ClampedCurrentValue());
				return currentValue;
			}

			GUI.changed = true;

			// We're outside the thumb, but inside the trough. 
			// If we have a scrollSize, we do pgup/pgdn style movements
			// if not, we just snap to the current position and begin tracking
			if (SupportsPageMovements())
			{
				SliderState().isDragging = false;
				GUI.nextScrollStepTime = SystemClock.now.AddMilliseconds(ScrollWaitDefinitions.firstWait);
				GUI.scrollTroughSide = CurrentScrollTroughSide();
				return PageMovementValue();
			}

			float newValue = ValueForCurrentMousePosition();
			StartDraggingWithValue(newValue);
			return Clamp(newValue);
		}

		private float OnMouseDrag()
		{
			if (GUIUtility.hotControl != id)
				return currentValue;

			var sliderState = SliderState();
			if (!sliderState.isDragging)
				return currentValue;

			GUI.changed = true;
			CurrentEvent().Use();

			// Recalculate the value from the mouse position. this has the side effect that values are relative to the
			// click point - no matter where inside the trough the original value was. Also means user can get back original value
			// if he drags back to start position.
			float deltaPos = MousePosition() - sliderState.dragStartPos;
			var newValue = sliderState.dragStartValue + deltaPos / ValuesPerPixel();
			return Clamp(newValue);
		}

		private float OnMouseUp()
		{
			if (GUIUtility.hotControl == id)
			{
				CurrentEvent().Use();
				GUIUtility.hotControl = 0;
			}
			return currentValue;
		}

		private float OnRepaint()
		{
			slider.Draw(position, GUIContent.none, id);
			thumb.Draw(ThumbRect(), GUIContent.none, id);

			if (GUIUtility.hotControl != id || !position.Contains(CurrentEvent().mousePosition) || IsEmptySlider())
				return currentValue;

			if (ThumbRect().Contains(CurrentEvent().mousePosition))
			{
				if (GUI.scrollTroughSide != 0) // if was scrolling with "trough" and the thumb reached mouse - sliding action over
				{
					GUIUtility.hotControl = 0;
				}

				return currentValue;
			}

			GUI.InternalRepaintEditorWindow();

			if (SystemClock.now < GUI.nextScrollStepTime)
				return currentValue;

			if (CurrentScrollTroughSide() != GUI.scrollTroughSide)
				return currentValue;

			GUI.nextScrollStepTime = SystemClock.now.AddMilliseconds(ScrollWaitDefinitions.regularWait);

			if (SupportsPageMovements())
			{
				SliderState().isDragging = false;
				GUI.changed = true;
				return PageMovementValue();
			}
			return ClampedCurrentValue();
		}

		private EventType CurrentEventType()
		{
			return CurrentEvent().GetTypeForControl(id);
		}

		private int CurrentScrollTroughSide()
		{
			float mousePos = horiz ? CurrentEvent().mousePosition.x : CurrentEvent().mousePosition.y;
			float thumbPos = horiz ? ThumbRect().x : ThumbRect().y;

			return mousePos > thumbPos ? 1 : -1;
		}

		private bool IsEmptySlider()
		{
			return start == end;
		}

		private bool SupportsPageMovements()
		{
			return size != 0 && GUI.usePageScrollbars;
		}

		private float PageMovementValue()
		{
			var newValue = currentValue;
			var sign = start > end ? -1 : 1;
			if (MousePosition() > PageUpMovementBound())
				newValue += size * sign * .9f;
			else
				newValue -= size * sign * .9f;
			return Clamp(newValue);
		}

		private float PageUpMovementBound()
		{
			if (horiz)
				return ThumbRect().xMax - position.x;
			return ThumbRect().yMax - position.y;
		}

		private Event CurrentEvent()
		{
			return Event.current;
		}

		private float ValueForCurrentMousePosition()
		{
			if (horiz)
				return (MousePosition() - ThumbRect().width * .5f) / ValuesPerPixel() + start - size * .5f;
			return (MousePosition() - ThumbRect().height * .5f) / ValuesPerPixel() + start - size * .5f;
		}

		private float Clamp(float value)
		{
			return Mathf.Clamp(value, MinValue(), MaxValue());
		}

		private Rect ThumbSelectionRect()
		{
			var selectionRect = ThumbRect();
#if UNITY_IPHONE
				int minSize = 12;
				if (selectionRect.width < minSize)
				{
					selectionRect.x -= (minSize - selectionRect.width) / 2;
					selectionRect.width = minSize;
				}
				if (selectionRect.height < minSize)
				{
					selectionRect.y -= (minSize - selectionRect.height) / 2;
					selectionRect.height = minSize;
				}
			
#endif
			return selectionRect;
		}

		private void StartDraggingWithValue(float dragStartValue)
		{
			var state = SliderState();
			state.dragStartPos = MousePosition();
			state.dragStartValue = dragStartValue;
			state.isDragging = true;
		}

		private SliderState SliderState()
		{
			return (SliderState)GUIUtility.GetStateObject(typeof(SliderState), id);
		}

		private Rect ThumbRect()
		{
            return horiz ? HorizontalThumbRect() : VerticalThumbRect();
       }

		private Rect VerticalThumbRect()
		{
			var valuesPerPixel = ValuesPerPixel();
			if (start < end)
				return new Rect(
					position.x + slider.padding.left,
					(ClampedCurrentValue() - start) * valuesPerPixel + position.y + slider.padding.top,
					position.width - slider.padding.horizontal,
					size * valuesPerPixel + ThumbSize());

			return new Rect(
				position.x + slider.padding.left,
				(ClampedCurrentValue() + size - start) * valuesPerPixel + position.y + slider.padding.top,
				position.width - slider.padding.horizontal,
				size * -valuesPerPixel + ThumbSize());
		}

		private Rect HorizontalThumbRect()
		{
			var valuesPerPixel = ValuesPerPixel();
			if (start < end)
				return new Rect(
					(ClampedCurrentValue() - start) * valuesPerPixel + position.x + slider.padding.left,
					position.y + slider.padding.top,
					size * valuesPerPixel + ThumbSize(),
					position.height - slider.padding.vertical);

			return new Rect(
				(ClampedCurrentValue() + size - start) * valuesPerPixel + position.x + slider.padding.left,
				position.y,
				size * -valuesPerPixel + ThumbSize(),
				position.height);
		}

		private float ClampedCurrentValue()
		{
			return Clamp(currentValue);
		}

		private float MousePosition()
		{
			if (horiz)
				return CurrentEvent().mousePosition.x - position.x;
			return CurrentEvent().mousePosition.y - position.y;
		}

		private float ValuesPerPixel()
		{
			if (horiz)
				return (position.width - slider.padding.horizontal - ThumbSize()) / (end - start);
			return (position.height - slider.padding.vertical - ThumbSize()) / (end - start);
		}

		private float ThumbSize()
		{
			if (horiz)
				return thumb.fixedWidth != 0 ? thumb.fixedWidth : thumb.padding.horizontal;
			return thumb.fixedHeight != 0 ? thumb.fixedHeight : thumb.padding.vertical;
		}

		private float MaxValue()
		{
			return Mathf.Max(start, end) - size;
		}

		private float MinValue()
		{
			return Mathf.Min(start, end);
		}
	}
}
